#include "stdafx.h"
#include "Tracer.h"
#include "GeometricTools.h"

#include "Vector2.h"
#include <iostream>

const float Tracer::TransparenceThreshold = 0.01f;
const float Tracer::ReflectionThreshold = 0.01f;
const float Tracer::RayThreshold = 0.01f;

Tracer::Tracer()
{
    m_nTraced = m_nTracedReflect = m_nTracedRefract = 0;

}


Tracer::~Tracer(void)
{
}


void Tracer::Init(Scene* pScene)
{
    m_pScene = pScene;
}

ColorValue Tracer::Trace(Line& incidentRay, unsigned int depth, Material& currentMaterial)
{


	ColorValue color;
	DoTrace(incidentRay, depth, 1.0f, currentMaterial, &color);
	return color;
}

void Tracer::DoTrace(Line& incidentRay, unsigned int depth, float weight, Material& currentMaterial, ColorValue* outColor)
{
	if (depth == 0) 
	{
		*outColor = ColorValue::Black; // FIXME can be set to background color
		return;
	}

	if (weight < RayThreshold) {
		*outColor = ColorValue::Black; // FIXME can be set to background color
		return;
	}

	if (weight > 1)
	{ // FIXME
		std::cout << "weight > 1" << std::endl;
	}

    m_nTraced++;

	Triangle triangle;
    ColorValue finalColor;
    *outColor = ColorValue::Black;
   
	IntersectInfo info;
    bool bIntersect = m_pScene->Intersect(&incidentRay, &info);

    if (bIntersect)
    {	
        Vector3f intersectionPoint = info.fPoint;
        Vector3f surfaceNormal = info.vNormal;
        Material material = *info.pMaterial;
		// move a bit outside to get rid of floating point problems.
		intersectionPoint = intersectionPoint + surfaceNormal * GeometricTools::FloatSmallValue;

        // ambiemt
        //finalColor += m_pScene->GetAmbientColor() * AmbientColorContributionRate(material);


        float ff = Dot(-incidentRay.GetDirection(), surfaceNormal);
        if (ff < -0.01) // FIXME why can LineIntersectSphere return surfaceNormal like this?
        {
            // It only happen when trace more then once.
            // Fixed. See  LineIntersectSphere() in GeometricTools.cpp.
            // Notice that the refraction light will lead this happened.
            //std::cout << "Dot(-incidentRay.GetDirection(), surfaceNormal) < 0, return by intersection calculation. depth: " << depth << std::endl;
        }

		float alpha = CalculateFresnelAlpha(material, incidentRay, surfaceNormal);


        // diffusion
        finalColor += CalculateAllDiffuseAndSpecularColor(weight, 
            incidentRay, triangle, intersectionPoint, surfaceNormal, material, alpha);


        // reflection
        ColorValue reflectionColor;
        Line reflectionRay;
        if (weight * ReflectionColorContributionRate(material, alpha) > RayThreshold 
            && ReflectionColorContributionRate(material, alpha) > ReflectionThreshold)
        {
            m_nTracedReflect++;
            GeometricTools::Reflect(incidentRay, triangle, intersectionPoint, surfaceNormal, &reflectionRay);
            Material tempMat = material;
            DoTrace(reflectionRay, depth - 1, weight * ReflectionColorContributionRate(material, alpha),
                tempMat, &reflectionColor);
            finalColor += reflectionColor;
        }

        // refraction
        ColorValue refractionColor;
        Line refractionRay;

        if (weight * RefractionContributionRate(material, alpha) > RayThreshold
            && RefractionContributionRate(material, alpha) > TransparenceThreshold)
        {
            m_nTracedRefract++;
            GeometricTools::Refract(incidentRay,
				triangle, intersectionPoint, surfaceNormal, currentMaterial, material, &refractionRay);
			if (Magnitude(refractionRay.GetDirection()) != 0) // if has a refraction ray
			{
				DoTrace(refractionRay, depth - 1, weight * RefractionContributionRate(material, alpha),
					material, &refractionColor);		
				finalColor += refractionColor;
			}
        }
    } 

    finalColor *= weight;
    *outColor = finalColor;
}

float Tracer::CalculateFresnelAlpha( Material& material, Line& incidentRay, Vector3f& surfaceNormal )
{
	// Fresnel reflection & refraction
	// http://en.wikipedia.org/wiki/Fresnel_equations
	// 
	// Here is a simplified implementation.

	//float sin = Magnitude(Cross(-incidentRay.GetDirection(), surfaceNormal));
	float eta = material.GetRefractiveIndex();
	float cos = Dot(-incidentRay.GetDirection(), surfaceNormal);
	if (cos < -GeometricTools::FloatSmallValue)
	{
		cos = -cos;
		eta = 1 / eta;
	}

	float alpha = material.GetAlpha();
	if (eta > 1)
	{
		// map [0, 1] to [alpha, 1] if (eta * (1 - cos) < 1), else alpha = 1.
		float t = eta * (1 - cos);
		alpha = alpha + (t < 1 ? t : 1) * (1 - alpha);
	}
	else
	{
		// map [0, 1] to [alpha, 1].
		alpha = alpha + (1 - cos) * (1 - alpha);
	}
	return alpha;
}

ColorValue Tracer::CalculateAllDiffuseAndSpecularColor(float weight, Line& incidentRay,
	Triangle& triangle, Vector3f& intersectionPoint, Vector3f& surfaceNormal, Material& material, float alpha)
{
	// calculate all color directly from light
	vector<Light*> lights = m_pScene->GetLights();
	ColorValue finalColor = ColorValue::Black;

	if (weight * DiffusionColorContributionRate(material, alpha) > RayThreshold
		|| weight * TransparenceContributionRate(material, alpha) > TransparenceThreshold
		|| weight * ReflectionColorContributionRate(material, alpha) > ReflectionThreshold)
	{

		for (size_t i = 0; i < lights.size(); ++i)
		{
			Light* light = lights[i];
			ColorValue currentColor = ColorValue::Black;
            Line lineLight(intersectionPoint, light->GetPosition());

            // Test if any object block the light
            if (!m_pScene->SegmentIntersect(&lineLight))
			{
				Vector2 textureCoordinate = GeometricTools::CalculateTextureCoordinate(triangle, intersectionPoint);
				// specular color
 				if (weight * ReflectionColorContributionRate(material, alpha) > ReflectionThreshold)
 				{
 					currentColor = CalculateSpecularColor(light, incidentRay, 
 						triangle, intersectionPoint, surfaceNormal, material, textureCoordinate);
 					currentColor *= ReflectionColorContributionRate(material, alpha);
 					finalColor += currentColor;
 				}

				// defuse color
				if (weight * DiffusionColorContributionRate(material, alpha) > RayThreshold)
				{
					currentColor = CalculateDiffuseColor(light, incidentRay, 
						triangle, intersectionPoint, surfaceNormal, material, textureCoordinate);
					currentColor *= DiffusionColorContributionRate(material, alpha);
					finalColor += currentColor;
				}
                
				// calculate back face color for transparence
				if (weight * TransparenceContributionRate(material, alpha) > TransparenceThreshold)
				{
					currentColor = CalculateTransparentDiffuseColor(light, incidentRay, 
						triangle, intersectionPoint, surfaceNormal, material, textureCoordinate);
					currentColor *= TransparenceContributionRate(material, alpha);
					finalColor += currentColor;
				}
			}
		}
	}
	return finalColor;
}

ColorValue Tracer::CalculateSpecularColor(Light* light, Line& incidentRay, Triangle& triangle,
	Vector3f& intersectionPoint, Vector3f& surfaceNormal, Material& material, Vector2& textureCoordinate)
{
    Vector3f lightIn = Normalize(light->GetPosition() - intersectionPoint);
	float lDn = Dot(lightIn, surfaceNormal);
	float iDn = Dot(-incidentRay.GetDirection(), surfaceNormal);
    if (lDn * iDn > 0)
    {
		// specular
		Line reflection;
		GeometricTools::Reflect(incidentRay, triangle, intersectionPoint, surfaceNormal, &reflection);
		float rDotI = Dot(reflection.GetDirection(), lightIn);
		float rDotV = rDotI > 0 ? rDotI : 0;

        ColorValue colorResult = light->GetColor() //* pow(rDotV, material.GetMass());
			* pow(rDotV, 100 * material.GetReflectivity())
			* (1 + material.GetReflectivity())
			* 0.6
			//* pow(material.GetReflectivity(), 0.5f)
			;

        return colorResult;
    }
    else
    {
        return ColorValue::Black;
    }
}

ColorValue Tracer::CalculateDiffuseColor(Light* light, Line& incidentRay, Triangle& triangle,
	Vector3f& intersectionPoint, Vector3f& surfaceNormal, Material& material, Vector2& textureCoordinate)
{
    Vector3f lightIn = Normalize(light->GetPosition() - intersectionPoint);
	float dot = Dot(lightIn, surfaceNormal);
    if (dot > 0)
    {
        ColorValue colorResult = light->GetColor() * material.GetColor(textureCoordinate) * dot;
        return colorResult;
    }
    else
    {
        return ColorValue::Black;
    }
}

ColorValue Tracer::CalculateTransparentDiffuseColor(Light* light, Line& incidentRay, Triangle& triangle,
	Vector3f& intersectionPoint, Vector3f& surfaceNormal, Material& material, Vector2& textureCoordinate)
{
	float dot = -Dot(Normalize(light->GetPosition() - intersectionPoint), surfaceNormal);
	return (dot > 0) ? (light->GetColor() * material.GetColor(textureCoordinate) * dot) : ColorValue::Black;
}
